package cn.com.jenson.service.file.service.impl;

import cn.com.jenson.common.ImportVerifierVo;
import cn.com.jenson.common.SelectVo;
import cn.com.jenson.dao.file.bo.FileBo;
import cn.com.jenson.dao.file.searchBo.FileSearchBo;
import cn.com.jenson.dao.file.vo.FileVo;
import cn.com.jenson.entity.file.File;
import cn.com.jenson.entity.file.QFile;
import cn.com.jenson.entity.file.Sharding;
import cn.com.jenson.exception.BusinessException;
import cn.com.jenson.service.file.repository.FileRepository;
import cn.com.jenson.service.file.repository.ShardingRepository;
import cn.com.jenson.service.file.service.FileService;
import cn.com.jenson.util.FileUtil;
import cn.com.jenson.util.ListUtil;
import cn.com.jenson.util.StringUtil;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.http.*;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
public class FileServiceImpl implements FileService {

    @Value("${upload.path}")
    private String uploadPath;

    @Resource
    private FileRepository fileRepository;

    @Resource
    private JPAQueryFactory jpaQueryFactory;

    @Resource
    private ShardingRepository shardingRepository;

    QFile qFile = QFile.file;

    @Override
    public Page<FileVo> pageAll(FileSearchBo fileSearchBo) {
        JPAQuery<File> jpaQuery = jpaQueryFactory
                .selectFrom(qFile)
                .where(getWhere(fileSearchBo));

        List<FileVo> fileVoList = new FileVo()
                .fromEntities(
                        jpaQuery.offset(fileSearchBo.getPageRequest().getOffset())
                                .limit(fileSearchBo.getPageRequest().getPageSize())
                                .fetch(),
                        FileVo.class
                );

        return new PageImpl<>(
                fileVoList,
                fileSearchBo.getPageRequest(),
                jpaQuery.fetchCount()
        );
    }

    @Override
    public List<FileVo> listAll(FileSearchBo fileSearchBo) {
        JPAQuery<File> jpaQuery = jpaQueryFactory
                .selectFrom(qFile)
                .where(getWhere(fileSearchBo));

        return new FileVo()
                .fromEntities(
                        jpaQuery.fetch(),
                        FileVo.class
                );
    }

    @Override
    public FileVo get(FileSearchBo fileSearchBo) throws InstantiationException, IllegalAccessException {
        File file = fileRepository.findByIdAndDeleted(fileSearchBo.getId(), false)
                .orElseThrow(() -> new BusinessException("找不到id为" + fileSearchBo.getId() + "的文件"));

        return new FileVo().fromEntity(file, FileVo.class);
    }
    @Override
    public FileVo get(Integer id) throws InstantiationException, IllegalAccessException {
        File file = fileRepository.findByIdAndDeleted(id, false)
                .orElseThrow(() -> new BusinessException("找不到id为" + id + "的文件"));

        return new FileVo().fromEntity(file, FileVo.class);
    }

    @Override
    public BooleanExpression getWhere(FileSearchBo fileSearchBo) {
        BooleanExpression where = qFile.deleted.eq(false);
        if (!ListUtil.isEmpty(fileSearchBo.getIdList())){
            where = where.and(qFile.id.in(fileSearchBo.getIdList()));
        }
        return where;
    }

    @Override
    public FileVo edit(FileBo fileBo) throws InstantiationException, IllegalAccessException {
        File file = new FileBo().toEntity(fileBo, File.class);
        file = fileRepository.save(file);
        return new FileVo().fromEntity(file, FileVo.class);
    }

    @Override
    public List<FileVo> editAll(List<FileBo> fileBos) {
        List<File> fileList = new FileBo().toEntities(fileBos, File.class);
        fileList = fileRepository.saveAll(fileList);
        return new FileVo().fromEntities(fileList, FileVo.class);
    }

    @Override
    public FileVo deleted(FileSearchBo fileSearchBo) throws InstantiationException, IllegalAccessException {
        File file = fileRepository.findByIdAndDeleted(fileSearchBo.getId(), false)
                .orElseThrow(() -> new BusinessException("找不到id为" + fileSearchBo.getId() + "的文件"));
        file.setDeleted(true);
        file = fileRepository.save(file);
        return new FileVo().fromEntity(file, FileVo.class);
    }

    @Override
    public List<FileVo> deletedAll(List<FileSearchBo> fileSearchBos) {
        List<Integer> ids = fileSearchBos.stream().map(FileSearchBo::getId).collect(Collectors.toList());
        List<File> fileList = fileRepository.findByIdInAndDeleted(ids, false);
        fileList = fileList.stream().peek(item -> item.setDeleted(true)).collect(Collectors.toList());
        fileList = fileRepository.saveAll(fileList);
        return new FileVo().fromEntities(fileList, FileVo.class);
    }

    @Override
    public List<ImportVerifierVo> importTemplateVerifiers() {
        return null;
    }

    @Override
    public void importTemplateDownload(HttpServletResponse response) throws IOException {

    }

    @Override
    public List<SelectVo<String, Integer, FileVo>> option() {
        return null;
    }

    @Override
    public void supplement(FileVo fileVo) {

    }

    @Override
    public void supplement(List<FileVo> fileVos) {

    }

    @Override
    public void supplement(Page<FileVo> fileVos) {

    }

    @Override
    public FileVo upload(FileBo fileBo) throws InstantiationException, IllegalAccessException, IOException {
        File file = fileRepository.findByMdKey(fileBo.getMdKey());
        if (Objects.nonNull(file) && file.getFinish()){
            FileVo fileVo = new FileVo().fromEntity(file, FileVo.class);
            fileVo.setCount(fileBo.getCount());
            fileVo.setNumber(fileBo.getCount());
            return fileVo;
        }
        if (Objects.isNull(file)) {
            file = new FileBo().toEntity(fileBo, File.class);
            file.setFinish(false);
            file.setRandomName(StringUtil.randomString(32) + "." + file.getFormat());
            file.setFilePath(uploadPath + file.getFileName());
            file = fileRepository.save(file);
        }
        fileBo.setId(file.getId());
        Sharding sharding = new Sharding(fileBo);

        if (fileBo.getFile().isEmpty())
            throw new BusinessException("文件不能为空");
        if (!FileUtil.checkDirExists(uploadPath)) {
            throw new BusinessException("上传目录不存在");
        }
        sharding.setFileName(StringUtil.randomString(32));
        sharding.setFilePath(uploadPath + sharding.getFileName());
        java.io.File localFile = new java.io.File(sharding.getFilePath());
        fileBo.getFile().transferTo(localFile);

        shardingRepository.save(sharding);
        if (!Objects.equals(sharding.getCount(), sharding.getNumber())) {
            FileVo fileVo = new FileVo().fromEntity(file, FileVo.class);
            fileVo.setCount(fileBo.getCount());
            fileVo.setNumber(fileBo.getNumber());
            return fileVo;
        }

        List<Sharding> shardingList = shardingRepository.findByFileIdAndDeleted(file.getId(), false);
        if (!FileUtil.mergeFile(file.getFilePath(), shardingList.stream().map(Sharding::getFilePath).collect(Collectors.toList()))) {
            throw new BusinessException("合并文件失败");
        }
        shardingList = shardingList.stream().peek(item -> item.setDeleted(true)).collect(Collectors.toList());
        shardingRepository.saveAll(shardingList);
        file.setFinish(true);
        file = fileRepository.save(file);
        FileVo fileVo = new FileVo().fromEntity(file, FileVo.class);
        fileVo.setCount(fileBo.getCount());
        fileVo.setNumber(fileBo.getCount());
        return fileVo;
    }

    @Override
    public ResponseEntity<InputStreamResource> download(FileSearchBo fileSearchBo) throws IOException, InstantiationException, IllegalAccessException {
        FileVo fileVo = get(fileSearchBo);
        String filePath = fileVo.getFilePath();
        FileSystemResource file = new FileSystemResource(filePath);
        return getInputStreamResourceResponseEntity(file);
    }

    @Override
    public ResponseEntity<InputStreamResource> download(Integer id) throws IOException, InstantiationException, IllegalAccessException {
        FileVo fileVo = get(id);
        String filePath = fileVo.getFilePath();
        FileSystemResource file = new FileSystemResource(filePath);
        return getInputStreamResourceResponseEntity(file, fileVo.getFileName());
    }

    private ResponseEntity<InputStreamResource> getInputStreamResourceResponseEntity(FileSystemResource file) throws IOException {
        return getInputStreamResourceResponseEntity(file, null);
    }

    private ResponseEntity<InputStreamResource> getInputStreamResourceResponseEntity(FileSystemResource file,String fileName) throws IOException {

        String name = fileName;
        if (Objects.isNull(name)) {
            name = URLEncoder.encode(
                    Objects.requireNonNull(file.getFilename()),
                    String.valueOf(StandardCharsets.UTF_8)
            );
        }
        // 设置响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setCacheControl("no-cache, no-store, must-revalidate");
        headers.setContentDisposition(
                ContentDisposition
                        .builder("inline")
                        .filename(name)
                        .build()
        );
        headers.setPragma("no-cache");
        headers.setExpires(0);

        MediaType mediaType;

        try {
            // 根据文件 url 猜测 ContentType
            mediaType = MediaType.parseMediaType(URLConnection.guessContentTypeFromName(file.getFilename()));
        } catch (InvalidMediaTypeException e) {
            if (Objects.requireNonNull(file.getFilename()).endsWith(".mp4")) {
                mediaType = MediaType.parseMediaType("video/mp4");
            } else {
                mediaType = MediaType.APPLICATION_OCTET_STREAM;
            }
        }

        return ResponseEntity
                .ok()
                .headers(headers)
                .contentLength(file.contentLength())
                .contentType(mediaType)
                .body(new InputStreamResource(file.getInputStream()));
    }

}
